import java.util.LinkedList;
import org.junit.runner.JUnitCore;
import org.junit.Test;
import org.json.*;
import static org.junit.Assert.*;
public class TestConcurrency
{
    public static void main(String[] args)
    {
        org.junit.runner.JUnitCore.main("TestConcurrency");        
    }

    @Test public void documentMap() {

        Document d = Document.get("test");
        Document e = Document.get("test");
        Document f = Document.get("something else");

        Document.remove("test");

        Document g = Document.get("test");
        Document h = Document.get("something else");

        assertNotNull("Always should return a value", d);
        assertNotNull("Always should return a value", f);
        assertNotNull("Always should return a value", g);

        assertSame("Same document id should result in same documents", d, e);
        assertSame("Same document id should result in same documents even if other documents are deleted", f, h);

        assertNotSame("Different ids => different documents", e, f);
        assertNotSame("Deleting a document gives a new meaning to the id", d, g);
    }

    @Test public void clientMap() {

        Client c = Client.addnew();
        final Client d = Client.addnew();
        Client e = Client.get(d.id);
        final Client f = Client.addnew();
        assertNotNull("Clients should be in the map",Client.remove(c.id));

        assertTrue("Created keys should exist", Client.containsKey(d.id));
        assertFalse("Removed cilents should not exists", Client.containsKey(c.id));
        assertFalse("Clients should have different id's", c.id == d.id);
        assertSame("Clients with same id should be the same", d, e);

        final LinkedList<Client> l = new LinkedList<Client>();

        Client.forEachClient(new ClientAction () {
            public void action (Client cl) {
                Client.remove(f.id);
                Client.remove(d.id);
                l.add(cl);
            }
        });

        assertTrue("Iteration should cover all clients", l.size() == 2);
    }

    @Test public void subscribe() throws Exception {
        
        Client c = Client.addnew();

        ConcurrencyHandler h = new ConcurrencyHandler();

        h.on_recieve(c, "[{type: subscribe, user_alias: fred, document_id: qwerty}]");

        String r = c.getResponse();
        JSONArray resps = new JSONArray(r);
        JSONObject resp = resps.getJSONObject(0);

        assertEquals("First a welcome message", resp.getString("type"), "welcome");
        assertEquals("With the requested user id", resp.getString("alias"), "fred");
        String cid = resp.getString("id");

        resp = resps.getJSONObject(1);
        assertEquals("Second a users message", resp.getString("type"), "users");
        
        JSONArray users = resp.getJSONArray("users");
        assertEquals("Only one user at the moment", users.length(), 1);

        JSONObject user = users.getJSONObject(0);
        assertEquals("The only user has our name", user.getString("alias"), "fred");
        assertEquals("The only user has our id", user.getString("id"), cid);

        h.on_timeout(c);

        Client.forEachClient(new ClientAction () {

            public void action (Client c) {
                assertFalse("No clients should be left", true);
            }

        });
    }

    @Test public void sync() throws Exception {

        Client c = Client.addnew();
        Client d = Client.addnew();

        ConcurrencyHandler h = new ConcurrencyHandler();

        h.on_recieve(c, "[{type: subscribe, user_alias: fred, document_id: qwerty}]");
        h.on_recieve(d, "[{type: subscribe, user_alias: george, document_id: qwerty}]");
        
        String r =  c.getResponse();

        JSONArray resps = new JSONArray(r);
        assertEquals("c Should have four messages - (welcome, users, presync, users)", resps.length(), 4);

        JSONObject resp = resps.getJSONObject(2);
        assertEquals(resp.getString("type"), "presync");

        String users = resps.getJSONObject(3).getJSONArray("users").toString();

        JSONArray dresps = new JSONArray(d.getResponse());

        assertEquals("d Should have two messages - (welcome, users)", 2, dresps.length());
        assertEquals("d and c should see the same list of users", users, dresps.getJSONObject(1).getJSONArray("users").toString()); 

        h.on_recieve(c, "[{type: sync, content: \"Hello world\"}]");

        r = c.getResponse();

        assertEquals("Sync messages should be sent to all clients", r, d.getResponse());

        resp = new JSONArray(r).getJSONObject(0);

        assertEquals("Should have recieved a sync message", resp.getString("type"), "sync");
        assertEquals("Should have recieved a sync message", resp.getString("content"), "Hello world");

        h.on_timeout(c);

        r = d.getResponse();

        resps = new JSONArray(r);
        
        assertEquals("d should have two messages - (sync, users)", resp.length(), 2);

        h.on_recieve(d, "[{type: unsubscribe}]");
    }

    @Test public void diff() throws Exception {
    
        Client c = Client.addnew();
        Client d = Client.addnew();
        Client e = Client.addnew();

        ConcurrencyHandler h = new ConcurrencyHandler();

        h.on_recieve(c, "[{type: subscribe, document_id: alpha, user_alias: beta}]");
        h.on_recieve(d, "[{type: subscribe, document_id: alpha, user_alias: abet}]");
        h.on_recieve(e, "[{type: subscribe, document_id: aleph, user_alias: beat}]");

        String r = e.getResponse();

        assertEquals("c should recieve only four (e isn't on our team)", new JSONArray(c.getResponse()).length(), 4);
        assertEquals("d should recieve only two (e isn't on our team)", new JSONArray(d.getResponse()).length(), 2);
        assertEquals("e should receieve two", new JSONArray(r).length(), 2);

        assertEquals("Only one user at the moment for e", new JSONArray(r).getJSONObject(1).getJSONArray("users").length(), 1);


        h.on_recieve(c, "[{type: diff, diff: [1,2,3]}]");
        h.on_recieve(d, "[{type: diff, diff: [3,2,1]}]");

        String cr = c.getResponse();
        String dr = d.getResponse();

        assertEquals("Both should see the same diff messages", cr, dr);

        JSONArray resps = new JSONArray(cr);

        assertEquals("two diffs only", resps.length(), 2);

        JSONObject resp = resps.getJSONObject(0);

        assertEquals("the first should be a diff message", resp.getString("type"), "diff");
        assertEquals("the first diff was from c", resp.getString("from"), c.id);
        assertEquals("the first diff was c's diff", resp.getJSONArray("diff").toString(), "[1,2,3]");

        resp = resps.getJSONObject(1);

        assertEquals("the second should be a diff message", resp.getString("type"), "diff");
        assertEquals("the second diff was from d", resp.getString("from"), d.id);
        assertEquals("the second diff was d's diff", resp.getJSONArray("diff").toString(), "[3,2,1]");
    }

	/*
	 * TODO:
	 * Test: sending multiple messages; sending nonsense; race-conditions?; run-time exceptions?
	 * (I mean, the code is perfect ;) but we can't prove it unless the tests are too)
	 */

}

